namespace CloudNinja.Metering.BlobLeaseUtil
{
    using System;
    using System.Reactive.Concurrency;
    using System.Reactive.Subjects;

    using CloudNinja.Metering.Common.Logging;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

    public class LicenseToRun : ILicenseToRun
    {
        private const int RetryBase = 2;
        private const int RetryBackoffStart = 30;
        private const int RetryBackoffEnd = 480;

        private readonly BlobLease blobLease;

        private readonly ILogger logger;

        private readonly Subject<bool> statusChangedSubject = new Subject<bool>();

        private string currentLeaseId;

        private bool running;

        private int retryCount;

        private readonly IScheduler retryScheduler;

        private TimeSpan getLicenseDueTime = TimeSpan.FromSeconds(Settings.LeaseRenewPeriod);

        public LicenseToRun(ILogger logger, RetryPolicy retryPolicy)
        {
            this.blobLease = new BlobLease(Settings.AzureStorageAccount.CreateCloudBlobClient(), Settings.LeasesContainerName, Settings.LeaseBlobName, logger, retryPolicy);
            this.logger = logger;
            this.retryScheduler = Scheduler.ThreadPool;
        }

        public IObservable<bool> StatusChanged
        {
            get
            {
                return this.statusChangedSubject;
            }
        }

        public bool Acquired
        {
            get
            {
                return this.currentLeaseId != null;
            }
        }

        public void StartGetLicense()
        {
            if (this.running)
            {
                return;
            }

            this.retryCount = 0;
            this.running = true;
            this.Loop();
        }

        public void StopGetLicense()
        {
            this.running = false;
            if (this.currentLeaseId != null)
            {
                this.blobLease.ReleaseLease();
                this.UpdateLease(null);
            }
        }

        public void Dispose()
        {
            this.statusChangedSubject.Dispose();
        }

        private void Loop()
        {
            if (!this.running)
            {
                return;
            }

            this.blobLease.AcquireLease();
            switch (this.blobLease.LeaseStatus)
            {
                case BlobLeaseStatus.Failed:
                    this.UpdateLease(null);
                    var retryNext = (int)Math.Pow(RetryBase, this.retryCount++) * RetryBackoffStart;
                    this.getLicenseDueTime =
                        TimeSpan.FromSeconds(retryNext > RetryBackoffEnd ? RetryBackoffEnd : retryNext);
                    break;
                case BlobLeaseStatus.Acquired:
                    this.UpdateLease(this.blobLease.LeaseId);
                    break;
            }

            this.retryScheduler.Schedule(this.getLicenseDueTime, this.Loop);
        }

        private void UpdateLease(string leaseId)
        {
            if (this.currentLeaseId == leaseId)
            {
                return;
            }

            this.currentLeaseId = leaseId;
            this.statusChangedSubject.OnNext(this.currentLeaseId != null);
        }
    }
}